// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/dialog',
    'base/js/events',
    'base/js/keyboard',
], function(IPython, $, utils, dialog, events, keyboard) {
    "use strict";

    var item_buttons_class = "item_buttons_inline";
    var item_buttons_selector = '.' + item_buttons_class;

    var NotebookList = function (selector, options) {
        /**
         * Constructor
         *
         * Parameters:
         *  selector: string
         *  options: dictionary
         *      Dictionary of keyword arguments.
         *          session_list: SessionList instance
         *          element_name: string
         *          base_url: string
         *          notebook_path: string
         *          contents: Contents instance
         */
        var that = this;
        this.session_list = options.session_list;
        // allow code re-use by just changing element_name in kernellist.js
        this.element_name = options.element_name || 'notebook';
        this.viewer = (options.viewer === "true");
        this.selector = selector;
        if (this.selector !== undefined) {
            this.element = $(selector);
            this.style();
            this.bind_events();
        }
        this.notebooks_list = [];
        this.sessions = {};
        this.base_url = options.base_url || utils.get_body_data("baseUrl");
        this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");

        // sbt-project-generation
        this.sbt_project_gen_enabled = options.sbt_project_gen_enabled || utils.get_body_data("sbt_project_gen_enabled");
        this.docker_repo = options.docker_repo || utils.get_body_data("dockerRepo");
        this.maintainer = options.maintainer || utils.get_body_data("maintainer");
        this.deploy_package_base = options.deploy_package_base || utils.get_body_data("deployPackageBase");
        this.mesos_version = options.mesos_version || utils.get_body_data("mesosVersion");

        this.contents = options.contents;
        if (this.session_list && this.session_list.events) {
            this.session_list.events.on('sessions_loaded.Dashboard',
                function(e, d) { that.sessions_loaded(d); });
        }
    };

    NotebookList.prototype.style = function () {
        var prefix = '#' + this.element_name;
        $(prefix + '_toolbar').addClass('list_toolbar');
        $(prefix + '_list_info').addClass('toolbar_info');
        $(prefix + '_buttons').addClass('toolbar_buttons');
        $(prefix + '_list_header').addClass('list_header');
        this.element.addClass("list_container");
    };

    NotebookList.prototype.bind_events = function () {
        var that = this;
        $('#refresh_' + this.element_name + '_list').click(function () {
            that.load_sessions();
        });
        this.element.bind('dragover', function () {
            return false;
        });
        this.element.bind('drop', function(event){
            that.handleFilesUpload(event,'drop');
            return false;
        });

        // Bind events for singleton controls.
        if (!NotebookList._bound_singletons) {
            NotebookList._bound_singletons = true;
            $('#new-file').click(function(e) {
                var w = window.open();
                that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) {
                    var url = utils.url_join_encode(
                        that.base_url, 'edit', data.path
                    );
                    w.location = url;
                });
                that.load_sessions();
            });
            $('#new-folder').click(function(e) {
                var name = prompt("Directory name:");
                that.contents.new_untitled(that.notebook_path || '', {type: 'directory', name: name})
                .then(function(){
                    that.load_list();
                });
            });
        }
    };

    NotebookList.prototype.handleFilesUpload =  function(event, dropOrForm) {
        var that = this;
        var files;
        if(dropOrForm =='drop'){
            files = event.originalEvent.dataTransfer.files;
        } else
        {
            files = event.originalEvent.target.files;
        }
        for (var i = 0; i < files.length; i++) {
            var f = files[i];
            var name_and_ext = utils.splitext(f.name);
            var is_notebook_file = utils.is_spark_notebook_file(f.name);

            var reader = new FileReader();
            if (is_notebook_file) {
                reader.readAsText(f);
            } else {
                // read non-notebook files as binary
                reader.readAsArrayBuffer(f);
            }
            var item = that.new_item(0);
            item.addClass('new-file');
            that.add_name_input(f.name, item, is_notebook_file ? 'notebook' : 'file');
            // Store the list item in the reader so we can use it later
            // to know which item it belongs to.
            $(reader).data('item', item);
            reader.onload = function (event) {
                var item = $(event.target).data('item');
                that.add_file_data(event.target.result, item);
                that.add_upload_button(item);
            };
            reader.onerror = function (event) {
                var item = $(event.target).data('item');
                var name = item.data('name');
                item.remove();
                dialog.modal({
                    title : 'Failed to read file',
                    body : "Failed to read file '" + name + "'",
                    buttons : {'OK' : { 'class' : 'btn-primary' }}
                });
            };
        }
        // Replace the file input form wth a clone of itself. This is required to
        // reset the form. Otherwise, if you upload a file, delete it and try to
        // upload it again, the changed event won't fire.
        var form = $('input.fileinput');
        form.replaceWith(form.clone(true));
        return false;
    };

    NotebookList.prototype.clear_list = function (remove_uploads) {
        /**
         * Clears the navigation tree.
         *
         * Parameters
         * remove_uploads: bool=False
         *      Should upload prompts also be removed from the tree.
         */
        if (remove_uploads) {
            this.element.children('.list_item').remove();
        } else {
            this.element.children('.list_item:not(.new-file)').remove();
        }
    };

    NotebookList.prototype.load_sessions = function(){
        this.session_list.load_sessions();
    };


    NotebookList.prototype.sessions_loaded = function(data){
        this.sessions = data;
        this.load_list();
    };

    NotebookList.prototype.load_list = function () {
        var that = this;
        this.contents.list_contents(that.notebook_path).then(
            $.proxy(this.draw_notebook_list, this),
            function(error) {
                that.draw_notebook_list({content: []}, "Server error: " + error.message);
            }
        );
    };

    /**
     * Draw the list of notebooks
     * @method draw_notebook_list
     * @param {Array} list An array of dictionaries representing files or
     *     directories.
     * @param {String} error_msg An error message
     */


    var type_order = {'directory':0,'notebook':1,'file':2};

    NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
        list.content.sort(function(a, b) {
            if (type_order[a['type']] < type_order[b['type']]) {
                return -1;
            }
            if (type_order[a['type']] > type_order[b['type']]) {
                return 1;
            }
            if (a['name'] < b['name']) {
                return -1;
            }
            if (a['name'] > b['name']) {
                return 1;
            }
            return 0;
        });
        var message = error_msg || 'Notebook list empty.';
        var item = null;
        var model = null;
        var len = list.content.length;
        this.clear_list();
        var n_uploads = this.element.children('.list_item').length;
        if (len === 0) {
            item = this.new_item(0);
            var span12 = item.children().first();
            span12.empty();
            span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
        }
        var path = this.notebook_path;
        var offset = n_uploads;
        if (path !== '') {
            item = this.new_item(offset);
            model = {
                type: 'directory',
                name: '..',
                path: utils.url_path_split(path)[0],
            };
            this.add_link(model, item);
            offset += 1;
        }
        for (var i=0; i<len; i++) {
            model = list.content[i];
            item = this.new_item(i+offset);
            this.add_link(model, item);
        }
        // Trigger an event when we've finished drawing the notebook list.
        events.trigger('draw_notebook_list.NotebookList');
    };


    NotebookList.prototype.new_item = function (index) {
        var item = $('<div/>').addClass("list_item").addClass("row");
        // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
        // item.css('border-top-style','none');
        item.append($("<div/>").addClass("col-md-12").append(
            $('<i/>').addClass('item_icon')
        ).append(
            $("<a/>").addClass("item_link").append(
                $("<span/>").addClass("item_name")
            )
        ).append(
            $('<div/>').addClass(item_buttons_class)
        ));

        if (index === -1) {
            this.element.append(item);
        } else {
            this.element.children().eq(index).after(item);
        }
        return item;
    };


    NotebookList.icons = {
        directory: 'folder_icon',
        notebook: 'notebook_icon',
        file: 'file_icon',
    };

    NotebookList.uri_prefixes = {
        directory: 'tree',
        notebook: 'notebooks',
        file: 'edit',
    };


    NotebookList.prototype.maybe_read_only_url = function(url, force) {
        if (force || this.viewer) {
            return  url+'?read_only=1';
        } else {
            return url;
        }
    };

    NotebookList.prototype.add_link = function (model, item) {
        var id = model.id,
            path = model.path,
            name = model.name;
        item.data('name', name);
        item.data('id', id);
        item.data('path', path);
        item.find(".item_name").text(name);
        var icon = NotebookList.icons[model.type];
        var uri_prefix = NotebookList.uri_prefixes[model.type];
        item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
        var link = item.find("a.item_link")
            .attr('href',
                this.maybe_read_only_url(
                    utils.url_join_encode(
                        this.base_url,
                        uri_prefix,
                        path
                    )
                )
            );
        // directory nav doesn't open new tabs
        // files, notebooks do
        if (model.type !== "directory") {
            link.attr('target','_blank');
        }
        if (model.type !== 'directory' && !this.viewer) {
            this.add_duplicate_button(item);
        }
        if (model.type == 'directory' && !this.viewer) {
          this.add_delete_directory_button(item);
        }
        if (model.type == 'file' && !this.viewer) {
            this.add_delete_button(item);
        } else if (model.type == 'notebook' && !this.viewer) {
            this.add_view_button(model, item, path);
            if (this.sessions[path] === undefined){
                this.add_delete_button(item);
            } else {
                this.add_shutdown_button(item, this.sessions[path]);
            }
            if (this.sbt_project_gen_enabled === "true") {
                this.add_generate_button(item);
            }
        }
    };


    NotebookList.prototype.add_name_input = function (name, item, icon_type) {
        item.data('name', name);
        item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
        item.find(".item_name").empty().append(
            $('<input/>')
            .addClass("filename_input")
            .attr('value', name)
            .attr('size', '30')
            .attr('type', 'text')
            .keyup(function(event){
                if(event.keyCode == 13){item.find('.upload_button').click();}
                else if(event.keyCode == 27){item.remove();}
            })
        );
    };


    NotebookList.prototype.add_file_data = function (data, item) {
        item.data('filedata', data);
    };


    NotebookList.prototype.add_shutdown_button = function (item, session) {
        var that = this;
        var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-warning").
            click(function (e) {
                var settings = {
                    processData : false,
                    cache : false,
                    type : "DELETE",
                    dataType : "json",
                    success : function () {
                        that.load_sessions();
                    },
                    error : utils.log_ajax_error,
                };
                var url = utils.url_join_encode(
                    that.base_url,
                    'api/sessions',
                    session
                );
                $.ajax(url, settings);
                return false;
            });
        item.find(item_buttons_selector).append(shutdown_button);
    };

    NotebookList.prototype.add_view_button = function (model, item, path) {
        var uri_prefix = NotebookList.uri_prefixes[model.type];
        var url = utils.url_join_encode(
            this.base_url,
            uri_prefix,
            path
        );
        url = this.maybe_read_only_url(url, true)
        var view_button = $("<a target='_blank' href="+url+"/>").html("<span class='text text-warning'>View (read-only)</span>").addClass("btn btn-default btn-xs");
        item.find(item_buttons_selector).prepend(view_button);
    };

    // toISOString
    // official toISOString returns the seconds and milliseconds we don't include here
    function pad(number) {
      if ( number < 10 ) {
        return '0' + number;
      }
      return number;
    }
    function ISODate() {
      var d = new Date();
      return d.getUTCFullYear() +
        '-' + pad( d.getUTCMonth() + 1 ) +
        '-' + pad( d.getUTCDate() ) +
        'T' + pad( d.getUTCHours() ) +
        ':' + pad( d.getUTCMinutes() ) +
        //':' + pad( d.getUTCSeconds() ) +
        //'.' + (d.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
        'Z';
    };
    // \\ toISOString

    NotebookList.prototype.add_generate_button = function (item) {
        var notebooklist = this;
        var generate_button = $("<button/>").text("Generate SBT project").addClass("btn btn-default btn-xs btn-success").
            click(function (e) {
                // $(this) is the button that was clicked.
                var that = $(this);
                var name = item.data('name');
                var path = item.data('path');
                var cronDef = ISODate();
                var destDir = utils.get_fullpath_notebook_title(path).replace(/\W+/g, "__");
                var message = $(
                    '<div>'+
                        '<div class="hidden">'+
                            '<label><strong>In which directory do you want to generate an SBT project?</strong></label>'+
                            '<br>'+
                            '<input class="to_dir" type="text" value="' + destDir + '" style="width: 85%" />'+
                        '</div>'+

                        '<label><strong>What is the package for the classes?</strong></label>'+
                        '<br>'+
                        '<input class="pkg" type="text" style="width: 85%" value="'+notebooklist.deploy_package_base+'"/>'+

                        '<label><strong>What is the version of the project?</strong></label>'+
                        '<br>'+
                        '<input class="version" type="text" style="width: 85%" value="0.0.1-SNAPSHOT"/>'+

                        '<div class="hidden sbt-gen-optional">'+
                            '<label><strong>Who is the maintainer of the project?</strong></label>'+
                            '<br>'+
                            '<input class="maintainer" type="text" style="width: 85%" value="'+notebooklist.maintainer+'"/>'+
                        '</div>'+

                        '<div class="hidden sbt-gen-optional">'+
                            '<label><strong>What is the docker repo for the generated project?</strong></label>'+
                            '<br>'+
                            '<input class="docker_repo" type="text" style="width: 85%" value="'+notebooklist.docker_repo+'"/>'+
                        '</div>'+

                        '<div class="hidden sbt-gen-optional">'+
                            '<label><strong>What is the mesos version for the generated project?</strong></label>'+
                            '<br>'+
                            '<input class="mesos_version" type="text" style="width: 85%" value="'+notebooklist.mesos_version+'"/>'+
                        '</div>'+

                        '<div>'+
                            '<a href="#" id="sbt-gen-more-settings-showhide">More settings (show/hide)</a>'+
                        '</div>'+
                    '</div>'
                );
                message.find("#sbt-gen-more-settings-showhide").click(function (event) {
                  event.preventDefault();
                  $(".sbt-gen-optional").toggleClass("hidden");
                });
                IPython.dialog.modal({
                    title : "Generate SBT project for " + name,
                    body : message,
                    buttons : {
                        Generate : {
                            class: "btn-primary",
                            click: function() {
                                var to_dir             = this.find("input.to_dir").val();
                                var pkg                = this.find("input.pkg").val();
                                var version            = this.find("input.version").val();
                                var maintainer         = this.find("input.maintainer").val();
                                var docker_repo        = this.find("input.docker_repo").val();
                                var mesos_version      = this.find("input.mesos_version").val();
                                notebooklist.generate_nb_project(
                                                            path, pkg, version, maintainer,
                                                            docker_repo, mesos_version, to_dir
                                                        ).then(
                                                            function() {
                                                                notebooklist.project_generated(name, to_dir);
                                                            }
                                                        );
                            }
                        },
                        Cancel : {}
                    }
                });
                return false;
            });
        item.find(item_buttons_selector).append(generate_button);
    };

    /**
    * Generate the notebook job/service project at `nb_path` into a given directory `to_dir` via POST
    */
    NotebookList.prototype.generate_nb_project = function (nb_path, pkg, version, maintainer,
                                                       docker_repo, mesos_version, to_dir) {
        var url = utils.url_join_encode(this.base_url, 'adastyx', nb_path, 'generate_nb_project');
        var settings = {
          processData: false,
          type: "POST",
          data: JSON.stringify({
            type: "notebook",
            pkg : pkg,
            version : version,
            maintainer : maintainer,
            docker_repo : docker_repo,
            mesos_version : mesos_version,
            to_dir: to_dir
          }),
          dataType: "json",
        };
        return utils.promising_ajax(url, settings);
    };

    NotebookList.prototype.project_generated = function(name, to) {
        $('#tabs a[href=#projects]').tab('show');
        var alertMsg = "Project " + name + " will be generated in '" + to + "' dir!";
        var alert = $('<div class="alert alert-success alert-dismissible" role="alert" style="margin: 5px;">\
           <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>\
           <strong>' + alertMsg + '.\
         </div>');
        $('#projects').prepend(alert);
        setInterval(function(){ $('#refresh_project_list').click(); }, 500);
        // alert(alertMsg);
    };

    NotebookList.prototype.add_duplicate_button = function (item) {
        var notebooklist = this;
        var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
            click(function (e) {
                // $(this) is the button that was clicked.
                var that = $(this);
                var name = item.data('name');
                var path = item.data('path');
                var message = 'Are you sure you want to duplicate ' + name + '?';
                var copy_from = {type: "notebook", copy_from : path};
                IPython.dialog.modal({
                    title : "Duplicate " + name,
                    body : message,
                    buttons : {
                        Duplicate : {
                            class: "btn-primary",
                            click: function() {
                                notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
                                    notebooklist.load_list();
                                });
                            }
                        },
                        Cancel : {}
                    }
                });
                return false;
            });
        item.find(item_buttons_selector).append(duplicate_button);
    };

    NotebookList.prototype.add_delete_button = function (item) {
        var notebooklist = this;
        var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
            click(function (e) {
                // $(this) is the button that was clicked.
                var that = $(this);
                // We use the filename from the parent list_item element's
                // data because the outer scope's values change as we iterate through the loop.
                var parent_item = that.parents('div.list_item');
                var name = parent_item.data('name');
                var path = parent_item.data('path');
                var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
                dialog.modal({
                    title : "Delete file",
                    body : message,
                    buttons : {
                        Delete : {
                            class: "btn-danger",
                            click: function() {
                                notebooklist.contents.delete(path).then(
                                    function() {
                                        notebooklist.notebook_deleted(path);
                                    }
                                );
                            }
                        },
                        Cancel : {}
                    }
                });
                return false;
            });
        item.find(item_buttons_selector).append(delete_button);
    };

  NotebookList.prototype.add_delete_directory_button = function (item) {
    var notebooklist = this;
    var delete_button = $("<button/>").text("Delete directory").addClass("btn btn-default btn-xs").
    click(function (e) {
      // $(this) is the button that was clicked.
      var that = $(this);
      // We use the filename from the parent list_item element's
      // data because the outer scope's values change as we iterate through the loop.
      var parent_item = that.parents('div.list_item');
      var name = parent_item.data('name');
      var path = parent_item.data('path');
      var message = 'Are you sure you want to permanently delete the directory: ' + name + '?';
      dialog.modal({
        title : "Delete directory",
        body : message,
        buttons : {
          Delete : {
            class: "btn-danger",
            click: function() {
              notebooklist.contents.delete(path).then(
                function() {
                  notebooklist.notebook_deleted(path);
                }
              );
            }
          },
          Cancel : {}
        }
      });
      return false;
    });
    item.find(item_buttons_selector).append(delete_button);
  };

    NotebookList.prototype.notebook_deleted = function(path) {
        /**
         * Remove the deleted notebook.
         */
        $( ":data(path)" ).each(function() {
            var element = $(this);
            if (element.data("path") == path) {
                element.remove();
                events.trigger('notebook_deleted.NotebookList');
            }
        });
    };


    NotebookList.prototype.add_upload_button = function (item) {
        var that = this;
        var upload_button = $('<button/>').text("Upload")
            .addClass('btn btn-primary btn-xs upload_button')
            .click(function (e) {
                var filename = item.find('.item_name > input').val();
                var path = utils.url_path_join(that.notebook_path, filename);
                var filedata = item.data('filedata');
                var format = 'text';
                if (filename.length === 0 || filename[0] === '.') {
                    dialog.modal({
                        title : 'Invalid file name',
                        body : "File names must be at least one character and not start with a dot",
                        buttons : {'OK' : { 'class' : 'btn-primary' }}
                    });
                    return false;
                }
                if (filedata instanceof ArrayBuffer) {
                    // base64-encode binary file data
                    var bytes = '';
                    var buf = new Uint8Array(filedata);
                    var nbytes = buf.byteLength;
                    for (var i=0; i<nbytes; i++) {
                        bytes += String.fromCharCode(buf[i]);
                    }
                    filedata = btoa(bytes);
                    format = 'base64';
                }
                var model = {};

                var is_notebook = utils.is_spark_notebook_file(filename);
                var content_type;
                if (is_notebook) {
                    model.type = 'notebook';
                    model.format = 'json';
                    try {
                        model.content = JSON.parse(filedata);
                    } catch (e) {
                        dialog.modal({
                            title : 'Cannot upload invalid Notebook',
                            body : "The error was: " + e,
                            buttons : {'OK' : {
                                'class' : 'btn-primary',
                                click: function () {
                                    item.remove();
                                }
                            }}
                        });
                        return false;
                    }
                    content_type = 'application/json';
                } else {
                    model.type = 'file';
                    model.format = format;
                    model.content = filedata;
                    content_type = 'application/octet-stream';
                }
                filedata = item.data('filedata');

                var on_success = function () {
                    item.removeClass('new-file');
                    that.add_link(model, item);
                    that.add_delete_button(item);
                    that.session_list.load_sessions();
                };

                var on_error = function(e) {
                    dialog.modal({
                        title : 'Cannot upload the file',
                        body : "The error was: " + e.xhr.responseText,
                        buttons : {'OK' : {
                            'class' : 'btn-primary',
                            click: function () {
                                item.remove();
                            }
                        }}
                    });
                }

                var exists = false;
                $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
                    if ($(v).data('name') === filename) { exists = true; return false; }
                });

                if (exists) {
                    dialog.modal({
                        title : "Replace file",
                        body : 'There is already a file named ' + filename + ', do you want to replace it?',
                        buttons : {
                            Overwrite : {
                                class: "btn-danger",
                                click: function () {
                                    that.contents.save(path, model).then(on_success).catch(on_error);
                                }
                            },
                            Cancel : {
                                click: function() { item.remove(); }
                            }
                        }
                    });
                } else {
                    that.contents.save(path, model).then(on_success).catch(on_error);
                }

                return false;
            });
        var cancel_button = $('<button/>').text("Cancel")
            .addClass("btn btn-default btn-xs")
            .click(function (e) {
                item.remove();
                return false;
            });
        item.find(item_buttons_selector).empty()
            .append(upload_button)
            .append(cancel_button);
    };


    // Backwards compatability.
    IPython.NotebookList = NotebookList;

    return {'NotebookList': NotebookList};
});
